package com.sshtools.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Properties;
import java.util.concurrent.Callable;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

import tsif.utils.ThreadUtils;

/**
 * ssh 工具<br>
 * 使用jsch-0.1.51.jar
 */
public class SSHUtils {

	/**
	 * 连接ssh<br>
	 * 使用完毕需要调用session.disconnect()
	 **/
	public static Session connect(String host, int port, String username, String password, String keyFile, int timeout) throws Exception {
		JSch jsch = new JSch();
		Session session = jsch.getSession(username, host, port);
		// 密码登陆
		if (keyFile == null || keyFile.length() <= 0) {
			// 用密码登陆
			session.setPassword(password);
		} else {
			// 用密钥登陆
			jsch.addIdentity(keyFile);
		}
		// 参数配置
		Properties config = new Properties();
		config.put("StrictHostKeyChecking", "no");
		session.setConfig(config);

		// 超时时间
		timeout = Math.max(timeout, 10000);

		// 执行连接
		session.connect(timeout);
		return session;
	}

	/** 上传处理 **/
	public static String execUpload(Session session, File file, String remotePath) throws Exception {
		// 检测如果是文件夹
		if (file.isDirectory()) {
			// remotePath = remotePath + File.separator + file.getName(); // 目录结构, 对远程目录进行调整.
			// 文件目录上传
			StringBuilder strBdr = new StringBuilder();
			// 遍历文件
			File[] files = file.listFiles();
			for (File f : files) {
				if (f.getName().startsWith(".")) {
					continue; // 跳过当前目录
				}
				String childRemotePath = remotePath;
				if (f.isDirectory()) {
					childRemotePath = remotePath + File.separator + f.getName();
				}
				// 上传子文件
				String str = execUpload(session, f, childRemotePath);
				strBdr.append(str);
			}
			return strBdr.toString();
		}
		// 整理路径
		remotePath = remotePath.replaceAll("\\\\", "/");
		// Log.debug("------------> execUpload: " + file.getAbsolutePath() + " -> " + remotePath);
		// if (true) {
		// return "";
		// }

		// 创建文件夹
		StringBuilder strBdr = new StringBuilder();
		String retCmdStr = SSHUtils.execCmd(session, "mkdir -vp " + remotePath);
		strBdr.append(retCmdStr);

		ChannelExec channel = null;
		ByteArrayOutputStream errStream = null;
		InputStream in = null;
		OutputStream out = null;
		try {

			// exec 'scp -t rfile' remotely
			String command = "scp -Cp -t " + remotePath;
			channel = (ChannelExec) session.openChannel("exec");
			channel.setCommand(command);

			// 设置错误输出流
			errStream = new ByteArrayOutputStream();
			channel.setErrStream(errStream);

			// 获取输入流(回馈内容)
			in = channel.getInputStream();
			channel.connect();

			// get I/O streams for remote scp
			out = channel.getOutputStream();

			// 等待回调内容
			final int waitMaxTime = 10 * 1000;
			waitResp(errStream, in, waitMaxTime);

			// 检测连接
			if (checkResq(in) != 0) {
				throw new RuntimeException("连接失败");
			}

			// command = "T " + (_lfile.lastModified() / 1000) + " 0";
			// The access time should be sent here,
			// but it is not accessible with JavaAPI ;-<
			// command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
			// command += " \n";
			// out.write(command.getBytes());
			// out.flush();
			// checkAck(in);

			// 申请文件空间
			// send "C0644 filesize filename", where filename should not include

			// 获取文件信息
			String lfile = file.getPath();
			String fileName = file.getName();
			long filesize = file.length();
			// 发送指令
			command = "C0644 " + filesize + " " + fileName + "\n";
			out.write(command.getBytes());
			out.flush();
			checkResq(in);

			// 读取文件并发送文件
			FileInputStream fis = new FileInputStream(lfile);
			byte[] buf = new byte[1024];
			while (true) {
				int len = fis.read(buf, 0, buf.length);
				if (len <= 0) {
					break;
				}
				out.write(buf, 0, len); // out.flush();
			}
			fis.close();
			fis = null;
			// 输入结束符号
			buf[0] = 0;
			out.write(buf, 0, 1);
			out.flush();

			// 检测回馈
			checkResq(in);

			// 读取错误输出
			String errStr = readSyncString(errStream, waitMaxTime);

			// 文本返回
			String retStr = readSyncString(in, waitMaxTime);

			// 返回内容
			strBdr.append(errStr).append(retStr);
			return strBdr.toString();
		} finally {
			if (in != null) {
				in.close();
				in = null;
			}
			if (out != null) {
				out.close();
				out = null;
			}
			if (channel != null) {
				channel.disconnect();
				channel = null;
			}
		}

	}

	// /** 执行语句 **/
	// public static String execShell(Session session, String[] cmds) throws Exception {
	// ChannelShell channel = (ChannelShell) session.openChannel("shell");
	// // 输入输出流
	// InputStream inputStream = channel.getInputStream();
	// OutputStream outputStream = channel.getOutputStream();
	// // channel.setExtOutputStream(System.err);
	// // 连接
	// channel.connect();
	//
	// // 文本返回
	// String retStr = null;
	// StringBuilder strBdr = new StringBuilder();
	// // retStr = readString(inputStream);
	// // strBdr.append(retStr);
	//
	// // 遍历输入指令
	// for (String cmd : cmds) {
	// outputStream.write(cmd.getBytes());
	// outputStream.flush();
	//
	// // 等待返回
	// retStr = readString(inputStream);
	// strBdr.append(retStr);
	// }
	//
	// inputStream.close();
	// channel.disconnect();
	//
	// return strBdr.toString();
	// }

	/** 执行cmd操作 **/
	public static String execCmd(Session session, String command) throws Exception {
		return execCmd(session, command, "/");
	}

	/** 执行cmd操作 **/
	public static String execCmd(Session session, String command, String remotePath) throws Exception {
		ChannelExec channel = null;
		ByteArrayOutputStream errStream = null;
		InputStream in = null;
		try {
			// 执行
			channel = (ChannelExec) session.openChannel("exec");
			channel.setCommand("cd " + remotePath + " &&" + command);

			// 设置错误输出流
			errStream = new ByteArrayOutputStream();
			channel.setErrStream(errStream);
			// 获取输入流
			in = channel.getInputStream();
			channel.connect();

			// 等待回调内容
			final int waitMaxTime = 3 * 1000;
			waitResp(errStream, in, waitMaxTime);

			// 读取错误输出
			String errStr = readSyncString(errStream, waitMaxTime);

			// 文本返回
			String retStr = readSyncString(in, waitMaxTime);

			// 返回内容
			StringBuilder strBdr = new StringBuilder();
			strBdr.append(errStr).append(retStr);
			return strBdr.toString();
		} finally {
			if (in != null) {
				in.close();
				in = null;
			}
			if (channel != null) {
				channel.disconnect();
				channel = null;
			}
		}

	}

	/** 异步读取内容 **/
	private static String readSyncString(final Object stream, int waitMaxTime) {
		try {
			String retStr = ThreadUtils.sync(new Callable<String>() {
				@Override
				public String call() throws Exception {
					return readString(stream);
				}
			}, waitMaxTime);
			return retStr;
		} catch (Exception e) {
		}
		return "";
	}

	/** 获取内容 **/
	private static String readString(Object stream) throws IOException {
		// 根据类型处理
		if (ByteArrayOutputStream.class.isInstance(stream)) {
			// 读取错误输出
			ByteArrayOutputStream bstream = (ByteArrayOutputStream) stream;
			byte[] buffer = bstream.toByteArray();
			int bufSize = (buffer != null) ? buffer.length : 0;
			if (bufSize > 0) {
				String bufStr = new String(buffer);
				return bufStr;
			}
			return "";
		} else if (InputStream.class.isInstance(stream)) {
			InputStream bstream = (InputStream) stream;
			if (bstream.available() <= 0) {
				return "";
			}
			// 文本返回
			StringBuilder strBdr = new StringBuilder();
			BufferedReader reader = new BufferedReader(new InputStreamReader(bstream));
			while (true) {
				String line = reader.readLine();
				if (line == null) {
					break;
				}
				// System.out.println(line);
				strBdr.append(line).append("\r\n");
			}
			return strBdr.toString();
		}

		throw new IOException("error type: " + stream);
	}

	/** 检测回调 **/
	private static int checkResq(InputStream in) throws Exception {
		int b = in.read();
		// b may be 0 for success,
		// 1 for error,
		// 2 for fatal error,
		// -1
		// 检测结果
		if (b == 0) {
			return b;
		}
		if (b == -1) {
			return b;
		}

		if (b == 1 || b == 2) {
			StringBuffer sb = new StringBuffer();
			int c;
			do {
				c = in.read();
				sb.append((char) c);
			} while (c != '\n');
			if (b == 1) { // error
				throw new RuntimeException(sb.toString());
			}
			if (b == 2) { // fatal error
				throw new RuntimeException(sb.toString());
			}
		}
		return b;
	}

	/** 检测响应 **/
	private static void waitResp(ByteArrayOutputStream errStream, InputStream inputStream, int waitMaxTime) throws Exception {
		// 等待回调内容
		long startTime = System.currentTimeMillis();
		while (true) {
			// 检测内容
			if (errStream.size() > 0) {
				break; // 有数据
			}
			if (inputStream.available() > 0) {
				break; // 有数据
			}

			// 检测超时
			long nowTime = System.currentTimeMillis();
			long dt = nowTime - startTime;
			if (dt > waitMaxTime) {
				break;
			}
			Thread.sleep(100);
		}
	}

}
